package com.darkidiot.session;

import com.darkidiot.session.conf.Configuration;
import com.darkidiot.session.conf.Constant;
import com.darkidiot.session.conf.SerializeType;
import com.darkidiot.session.util.StringUtil;
import com.darkidiot.session.util.WebUtil;
import com.google.common.base.MoreObjects;
import com.google.common.base.Throwables;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;

/**
 * 分布式session filter 转换类
 * session attribute持久化到redis
 * Copyright (c) for darkidiot
 * Date:2017/4/14
 * Author: <a href="darkidiot@icloud.com">darkidiot</a>
 * School: CUIT
 * Desc:
 */
@Slf4j
public class DistributedSessionFilter implements Filter {
    private final DistributedSessionManager sessionManager;
    private String sessionCookieName;
    private int maxInactiveInterval;
    private String cookieDomain;
    private String cookieContextPath;
    private int cookieMaxAge;

    public DistributedSessionFilter() {
        Properties properties = new Properties();
        try (InputStream input = Resources.asByteSource(Resources.getResource(Constant.configurationFileName)).openStream()) {
            properties.load(input);
        } catch (Exception e) {
            log.error("failed to load {}, cause by:{}", Constant.configurationFileName, Throwables.getStackTraceAsString(e));
        }

        String sessionSource = properties.getProperty("session.source");
        Configuration configuration = new Configuration();
        if (!StringUtil.isEmpty(sessionSource) && !StringUtil.startsWithIgnoreCase(sessionSource, "$")) {
            log.info("DistributedSessionFilter set configuration[session.source] -> {}", sessionSource);
            configuration.setSource(sessionSource);
        }

        String sessionRedisSerializeType = properties.getProperty("session.serialize.type");
        if (!StringUtil.isEmpty(sessionRedisSerializeType) && !StringUtil.startsWithIgnoreCase(sessionRedisSerializeType, "$")) {
            log.info("DistributedSessionFilter set configuration[session.serialize.type] -> {}", sessionRedisSerializeType);
            configuration.setSerializeType(SerializeType.value(sessionRedisSerializeType));
        }

        String sessionRedisPrefix = properties.getProperty("session.redis.prefix");
        if (!StringUtil.isEmpty(sessionRedisPrefix) && !StringUtil.startsWithIgnoreCase(sessionRedisPrefix, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.prefix] -> {}", sessionRedisPrefix);
            configuration.setSessionRedisPrefix(sessionRedisPrefix);
        }

        String sessionRedisCluster = properties.getProperty("session.redis.cluster");
        if (!StringUtil.isEmpty(sessionRedisCluster) && !StringUtil.startsWithIgnoreCase(sessionRedisCluster, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.cluster] -> {}", sessionRedisCluster);
            configuration.setSessionRedisCluster(Boolean.valueOf(sessionRedisCluster));
        }

        String sessionRedisTestOnBorrow = properties.getProperty("session.redis.test.on.borrow");
        if (!StringUtil.isEmpty(sessionRedisTestOnBorrow) && !StringUtil.startsWithIgnoreCase(sessionRedisTestOnBorrow, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.test.on.borrow] -> {}", sessionRedisTestOnBorrow);
            configuration.setSessionRedisTestOnBorrow(Boolean.parseBoolean(sessionRedisTestOnBorrow));
        }

        String sessionRedisMaxIdle = properties.getProperty("session.redis.max.idle");
        if (!StringUtil.isEmpty(sessionRedisMaxIdle) && !StringUtil.startsWithIgnoreCase(sessionRedisMaxIdle, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.max.idle] -> {}", sessionRedisMaxIdle);
            configuration.setSessionRedisMaxIdle(Integer.parseInt(sessionRedisMaxIdle));
        }

        String sessionRedisMaxTotal = properties.getProperty("session.redis.max.total");
        if (!StringUtil.isEmpty(sessionRedisMaxTotal) && !StringUtil.startsWithIgnoreCase(sessionRedisMaxTotal, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.max.total] -> {}", sessionRedisMaxTotal);
            configuration.setSessionRedisMaxTotal(Integer.parseInt(sessionRedisMaxTotal));
        }

        String sessionRedisHost = properties.getProperty("session.redis.host");
        if (!StringUtil.isEmpty(sessionRedisHost) && !StringUtil.startsWithIgnoreCase(sessionRedisHost, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.host] -> {}", sessionRedisHost);
            configuration.setSessionRedisHost(sessionRedisHost);
        }

        String sessionRedisPort = properties.getProperty("session.redis.port");
        if (!StringUtil.isEmpty(sessionRedisPort) && !StringUtil.startsWithIgnoreCase(sessionRedisPort, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.port] -> {}", sessionRedisPort);
            configuration.setSessionRedisPort(Integer.parseInt(sessionRedisPort));
        }

        String sessionRedisSentinelHosts = properties.getProperty("session.redis.sentinel.hosts");
        if (!StringUtil.isEmpty(sessionRedisSentinelHosts) && !StringUtil.startsWithIgnoreCase(sessionRedisSentinelHosts, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.sentinel.hosts] -> {}", sessionRedisSentinelHosts);
            configuration.setSessionRedisSentinelHosts(sessionRedisSentinelHosts);
        }

        String sessionRedisSentinelMasterName = properties.getProperty("session.redis.sentinel.master.name");
        if (!StringUtil.isEmpty(sessionRedisSentinelMasterName) && !StringUtil.startsWithIgnoreCase(sessionRedisSentinelMasterName, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.sentinel.master.name] -> {}", sessionRedisSentinelMasterName);
            configuration.setSessionRedisSentinelMasterName(sessionRedisSentinelMasterName);
        }

        String sessionRedisDbIndex = properties.getProperty("session.redis.db.index");
        if (!StringUtil.isEmpty(sessionRedisDbIndex) && !StringUtil.startsWithIgnoreCase(sessionRedisDbIndex, "$")) {
            log.info("DistributedSessionFilter set configuration[session.redis.db.index] -> {}", sessionRedisDbIndex);
            configuration.setSessionRedisDbIndex(Integer.parseInt(sessionRedisDbIndex));
        }

        this.sessionManager = DistributedSessionManager.newInstance(configuration);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        try {
            initParameters(filterConfig);
        } catch (Exception e) {
            log.error("failed to init distributed session filter,cause by:{}", Throwables.getStackTraceAsString(e));
            throw new ServletException(e);
        }
    }

    private void initParameters(FilterConfig filterConfig) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        String sessionCookieNameParameter = "sessionCookieName";
        String maxInactiveIntervalParameter = "maxInactiveInterval";
        String cookieDomainParameter = "cookieDomain";
        String cookieContextPathParameter = "cookieContextPath";
        String cookieMaxAge = "cookieMaxAge";
        String temp = filterConfig.getInitParameter(sessionCookieNameParameter);
        sessionCookieName = temp == null ? "msid" : temp;
        temp = filterConfig.getInitParameter(maxInactiveIntervalParameter);
        maxInactiveInterval = temp == null ? 1800 : Integer.parseInt(temp);
        cookieDomain = filterConfig.getInitParameter(cookieDomainParameter);
        temp = filterConfig.getInitParameter(cookieContextPathParameter);
        cookieContextPath = temp == null ? "/" : temp;
        this.cookieMaxAge = Integer.parseInt(MoreObjects.firstNonNull(filterConfig.getInitParameter(cookieMaxAge), "-1"));
        log.info("CacheSessionFilter (sessionCookieName={},maxInactiveInterval={},cookieDomain={},cookieContextPath={},cookieMaxAge={})", sessionCookieName, maxInactiveInterval, cookieDomain, cookieContextPath, cookieMaxAge);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof DistributedHttpServletRequest) {
            chain.doFilter(request, response);
        } else {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            DistributedHttpServletRequest distributedRequest = new DistributedHttpServletRequest(httpRequest, httpResponse);
            distributedRequest.setSessionCookieName(sessionCookieName);
            distributedRequest.setMaxInactiveInterval(maxInactiveInterval);
            distributedRequest.setCookieDomain(cookieDomain);
            distributedRequest.setCookieContextPath(cookieContextPath);
            distributedRequest.setCookieMaxAge(cookieMaxAge);
            chain.doFilter(distributedRequest, response);
            DistributedSession session = distributedRequest.currentSession();
            if (session != null) {
                if (!session.isValid()) {
                    log.debug("delete login cookie");
                    WebUtil.emptyCookie(httpRequest, httpResponse, sessionCookieName, cookieDomain, cookieContextPath);
                } else if (session.isDirty()) {
                    log.debug("try to flush session to session store");
                    Map<String, Object> snapshot = session.snapshot();
                    if (sessionManager.save(session.getId(), snapshot, maxInactiveInterval)) {
                        log.debug("succeed to flush session {} to store, key is:{}", snapshot, session.getId());
                    } else {
                        log.error("failed to save session to redis");
                        WebUtil.emptyCookie(httpRequest, httpResponse, sessionCookieName, cookieDomain, cookieContextPath);
                    }
                } else {
                    sessionManager.refreshExpireTime(session.getId(), maxInactiveInterval);
                }
            }

        }
    }

    @Override
    public void destroy() {
        sessionManager.destroy();
    }
}
